package stream;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.testng.annotations.Test;


public class TheCollectors {

    static class Message {
        private int id;
        private String name;

        public Message(int id, String name) {
            this.id = id;
            this.name = name;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override public String toString() {
            return "Message{" + "id=" + id + ", name='" + name + '\'' + '}';
        }
    }

    /**
     * collectingAndThen，对生成的集合再做操作，如果元素有重复，会保留重复元素
     * groupingBy，根据分类函数分组，得到map，map的键是分类函数的返回值
     */
    @Test
    private void t1() {
        List<String> list = List.of("a", "ab", "bc", "aa", "c", "cb", "ad", "b", "a");
        Set<List<String>> collect = list.stream().collect(
            Collectors.collectingAndThen(
                Collectors.groupingBy(s -> s.contains("a")),
                map -> new HashSet<>(map.values()))
        );
        System.out.println(collect);

        // groupingBy的键就是分类函数的返回值
        Map<Boolean, List<String>> map = list.stream().collect(Collectors.groupingBy(s -> s.contains("a")));
        System.out.println(map);
    }

    /**
     * 对集合groupingBy分组之后的reduce操作
     */
    @Test
    private void t2() {
        Message m1 = new Message(1, "a");
        Message m2 = new Message(1, "c");
        Message m3 = new Message(1, "a");

        Message m4 = new Message(2, "b");
        Message m5 = new Message(2, "b");

        Message m6 = new Message(3, "c");
        Message m7 = new Message(1, "b");

        List<Message> list = List.of(m1, m2, m3, m4, m5, m6);
        // 根据id、name分组只有两组的list
        List<Message> list2 = List.of(m1, m2, m3, m4, m5);

        List<Message> list3 = List.of(m1, m2, m4, m5, m7);

        Map<String, List<Message>> map = list.stream().collect(Collectors.groupingBy(Message::getName));
        System.out.println("必定都是顺序输出");
        System.out.println(map);

        Set<Optional<Message>> collect = list.stream().collect(Collectors
            .collectingAndThen(
                // 先根据name分成a、b、c三组
                Collectors.groupingBy(Message::getName),
                // 对每组内的元素，两两结合，所以如果某个键对应的list里元素只有1个，那么对于这个单独的元素，reduce的accumulator累加器就不会生效
                // reduce会持续处理，这里只对分组内的持续处理
                map2 -> map2.values().stream()
                    .map(megs -> megs.stream()
                        .reduce((ms1, ms2) -> new Message(ms1.getId(), ms2.name + "-reduce")))
                // reduce需要的是二元操作符，只能传入两个参数，下面的是错误的写法
//              .map(megs -> megs.stream().reduce((ms1, ms2, ms3) -> new Message(ms1.getId(), ms2.name + "-reduce")))
//              .map(megs -> megs.stream().reduce(ms1 -> new Message(ms1.getId(), ms1.name + "-reduce")))
                .collect(Collectors.toSet())
            ));
        System.out.println("注意不是顺序执行的，输出不是name='a-reduce'、name='b-reduce'、name='c-reduce'，而是随机的");
        System.out.println(collect);

        System.out.println("---------------");
        // 分组不会出现某个键的list只有一个元素的情况
        Map<Integer, List<Message>> map2 = list2.stream().collect(Collectors.groupingBy(Message::getId));
        System.out.println(map2);
        Set<Optional<Message>> collect2 = list2.stream().collect(Collectors
            .collectingAndThen(
                Collectors.groupingBy(Message::getId),
                // 根据id分成两组，不会有单独的元素存在，元素都会被accumulator处理
                map3 -> map3.values().stream()
                    .map(megs -> megs.stream().reduce((ms1, ms2) -> new Message(ms1.getId(), ms2.name + "-reduce")))
                    .collect(Collectors.toSet())
            ));
        System.out.println(collect2);

        System.out.println("---------------");
        // 分组中有三个元素
        Map<Integer, List<Message>> map3 = list2.stream().collect(Collectors.groupingBy(Message::getId));
        System.out.println(map2);
        Set<Optional<Message>> collect3 = list2.stream().collect(Collectors
            .collectingAndThen(
                Collectors.groupingBy(Message::getId),
                // 根据id分成两组，不会有单独的元素存在，元素都会被accumulator处理
                map4 -> map3.values().stream()
                    .map(megs -> megs.stream().reduce((ms1, ms2) -> new Message(ms1.getId(), ms2.name + "-reduce")))
                    .collect(Collectors.toSet())
            ));
        System.out.println(collect3);
    }

    /**
     *
     */
    @Test
    private void t3() {
        Message2 message1 = new Message2(1, List.of("a"));
        Message2 message2 = new Message2(1, List.of("b"));
        Message2 message3 = new Message2(1, List.of("c"));

        Message2 message4 = new Message2(2, List.of("d"));
        Message2 message5 = new Message2(2, List.of("e"));

        Message2 message6 = new Message2(1, List.of("f"));

        List<Message2> msg1 = List.of(message1, message2, message3, message4, message5, message6);

        Map<Integer, List<Message2>> map1 = msg1.stream().collect(Collectors.groupingBy(Message2::getId));
        System.out.println(map1);

        Set<Optional<Message2>> collect = msg1.stream().collect(Collectors
            .collectingAndThen(
                Collectors.groupingBy(Message2::getId),
                map -> map.values().stream()
                    .map(mesg -> mesg.stream().reduce((m1, m2) -> new Message2(m1.getId(),
                    // 虽然看起来只操作了两个元素m1、m2，但是实际上concat会组合分组内所有的元素，这里串联起了4个元素
                    Stream.concat(m1.getMsgs().stream(), m2.getMsgs().stream()).collect(Collectors.toList()))))
                    .collect(Collectors.toSet())));

        System.out.println(collect);

//        msg1.stream().collect(
//            Collectors.collectingAndThen(
//                Collectors.groupingBy(Message2::getId),
//                map -> map.values().stream()
//                    .map(m2 -> {
//                        m2.stream().reduce((o1, o2) -> )
//                    })
//            )
//        )


    }


    @Test
    private static void t4() {

        var map = new HashMap<String, Integer>();
        map.put("a", 3);
        map.put("b", 5);
        map.put("c", 0);
        map.put("d", 2);

        var res = map.entrySet().stream()
            .collect(Collectors.groupingBy(entry -> entry.getValue() % 2));

        var res3 = map.entrySet().stream()
            .collect(
                Collectors.collectingAndThen(
                    Collectors.groupingBy(entry -> entry.getValue() % 2),
                    map3 -> new HashMap(map3)
                )
            );


        System.out.println(res);


//        var res2 = map.entrySet().stream()
//            .collect(
//                Collectors.collectingAndThen(
//                    Collectors.groupingBy(entry -> {
//                        T entry1 = entry;
//                        return entry1.getValue() % 2;
//                    }),
//                    map2 -> map2
//                ))

            ;



    }
}

class Message2 {

    private int id;
    private List<String> msgs;

    public Message2(int id, List<String> msgs) {
        this.id = id;
        this.msgs = msgs;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public List<String> getMsgs() {
        return msgs;
    }

    public void setMsgs(List<String> msgs) {
        this.msgs = msgs;
    }

    @Override public String toString() {
        return "Message2{" + "id=" + id + ", msgs=" + msgs + '}';
    }
}
